/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <cc_compat.h>
#include <stdio.h>
#include "timer.h"
#include "power.h"
#include "io.h"
#include "event.h"
#include "mppt.h"
#include "adc.h"
#include "ac.h"
#include "sdi.h"
#include "charger.h"

unsigned int 	xdata Vin;

// Data memory so the math is as fast as possible
long 			data Vin_avg;

static bool ac_adapter_in;
static bool prev_poweron_state;
static bool ac_monitor_active;
static bool ac_status;
static int ac_timer;

void ac_init(void)
{
	ac_timer = get_ms_timer();
	ac_monitor_enable(1);
	VIN_AVG_INIT;
}

static void ac_config_plugged(void)
{
	/* set AC in flag */
	ac_adapter_in = 1;
	print_tstamp();
	puts("AC In");
	event_send_event(EVENT_AC_CHANGE);

	// If we are off and auto power is set then return to the
	// previous on state
	if (BootFlags & AUTO_PWR_FLAG)
	{
		if (power_host_is_off())
		{
			if (prev_poweron_state)
			{
				puts("AP On");
				power_start_host();
			}
		}
	}
	// If we are off and const power is set then
	// turn on regardless
	if (BootFlags & CONST_PWR_FLAG)
	{
		if (power_host_is_off())
		{
			puts("CP On");
			power_start_host();
		}
	}
}

void ac_config_unplugged(void)
{
	/* clear AC flag and turn off charging */
	ac_adapter_in = 0;
	mppt_nice_log();
	print_tstamp();
	puts("AC Out");
	event_send_event(EVENT_AC_CHANGE);

	// Save the power state of the system
	// when we lose ext power so we know what
	// state to return to if AP or CP mfg tags
	// are enabled
	prev_poweron_state = !power_host_is_off();

}

void ac_process_adapter_status(void)
{

	unsigned char new_ac_status;
	static unsigned char debounce;

	if (!ac_monitor_active)
		return;


	if (!check_timer(ac_timer, 10))
		return;

	ac_timer = get_ms_timer();

	/* The H/W status must be read in first to make sure the state correct */
	new_ac_status = read_CHRG_AC_OK();
	if (ac_status != new_ac_status)
	{
		/* the status is changed, debounce method active */
		if (++debounce > 5)
		{
			/* the status is changed and stable, do something for AC in/out */
			ac_status = new_ac_status;
			if (new_ac_status)
			{
				ac_config_plugged();
			}
			else
			{
				ac_config_unplugged();
			}
		}
	}
	else
	{
		/* reset buffer for checking AC in/out */
		debounce = 0;
	}

	ac_adjust_input_power();
}

bool is_ac_adapter_in(void)
{
	return ac_adapter_in;
}

void ac_monitor_enable(bool active)
{
	ac_monitor_active = active;
	ac_status = !read_CHRG_AC_OK();  // force (re)detection
}

void ac_cmd_read_Vin(void)
{
	Vin = AVG_VIN;
	sdi_cmdresp_queue_add(Vin & 0xff);
	sdi_cmdresp_queue_add(Vin >> 8);
}

void ac_cmd_read_Vin_scaled(void)
{
	int voltage;
	voltage = VIN_IN_DECI_V;
	sdi_cmdresp_queue_add(voltage & 0xff);
	sdi_cmdresp_queue_add(voltage >> 8);
}

// Adjust the maximum input power based on the input voltage
// the ISL chip only limits current so we have to adust the maximum
// input current as the voltage increases.
void ac_adjust_input_power(void)
{
	unsigned int mic,newmic;
	int voltage;

	voltage = VIN_IN_DECI_V;

	// The charger dosen't work below 9V so three's not point in computing values
	// for voltages lower than that.  Plus the charger code can't deal with large
	// values when the voltage gets near zero.
	if (voltage <= 90) return;

	mic = ISL_get_max_input_current();

	// Max output voltage of the 24W adapter is 12.6V so
	// use 24W if the voltage is below 12.7V.

	// In theory.  In practice the input switcher has a 6% error
	// in its current regulation so 24W drops to 22.4W and
	// 25 becomes 23.3 to keep us below the limit for the adapters.
	if ( voltage < 127 )
	{
		newmic = (unsigned int)(224000/voltage);
	}
	// Otherwise we cap at 25W less error% (23.3W)
	else
	{
		newmic = (unsigned int)(233000/voltage);
	}

	// For now make current IC = max IC
	// Future plans are for an additional restriction that can be
	// set via a EC tag and allow actual IC to be different than
	// max.

	newmic = ISL_mA_to_input_current(newmic);
	if ( newmic != mic )
	{
//		printf("V: %d mic: %u newmic: %u\n",voltage,mic,newmic);
		ISL_set_max_input_current(newmic);

		// Setting the max will change the existing IC if it needs to be lowered
		// so only re-set if we need to rasise it back up.
		if (newmic > mic)
		{
			ISL_set_input_current(newmic);
		}
	}
}
